import { Component, OnInit, ElementRef, ViewChild, ViewChildren, EventEmitter } from '@angular/core';
import { CmHttp } from "http-module";
import { Http } from "@angular/http";
import { Subscription } from "rxjs/Subscription";
import { Observable } from "rxjs/Observable";
import { Subject } from "rxjs/Subject";
import { SocketService } from "./../../socket/socket.service";
import { BroadcastService, BROADCAST_TYPE } from "broadcast";
import { getHeightOfElem } from "./utils";
import { StockDetail, LoadingService } from "utils";
import * as _ from "lodash";

import { TableParams, TableOptions, QuoteHeaders, QuoteHeader } from "quote";

import "./../../shared/rxjs-operators";

const SET_SIZE = 0,
  UPDATE_DATA = 1,
  SET_OFFSET = 2,
  SET_STRICK = 3;

const SORT_LIST = [
  "last_px",
  "business_balance",
  "business_amount",
  "px_change",
  "px_change_rate",
  "amplitude",
  "turnover_ratio",
  "vol_ratio",
  "pe_rate",
  "dyn_pb_rate",
  "circulation_value",
  "business_amount_in",
  "business_amount_out",
  "entrust_rate"
];



const CM_HS_MAP = {
  "current": "last_px",
  "name": "prod_name",
  "volume": "business_amount",
  "change_rate": "px_change_rate",
  "change_amount": "px_change",
  "amount_in": "business_amount_in",
  "amount_out": "business_amount_out",
};

const covertCmToHs = cmData => {
  let map = {};
  for (let i in cmData) {
    map[CM_HS_MAP[i]] = cmData[i]
  }
  return {
    ...cmData,
    ...map
  };
}

const scaleToCent = (num: number) => num / 100;

const appendParams = (originUrl: string, key: string, value: string) => {
  if (!key || !value || !originUrl) return originUrl;
  return originUrl.search("\\?") === -1
    ? `${originUrl}?${key}=${value}`
    : `${originUrl}&${key}=${value}`;
}

const getPageNumByOffsetRate = (fullSize: number, perPage: number, offsetRate: number) => {
  return Math.floor(Math.floor(fullSize * offsetRate) / perPage) + 1;
}

export interface DynamicTableOptions {
  subscribe?: {
    enabled?: boolean,
    channels?: any[]
  };
  cacheSize: number;
  autoLoadMore: boolean;
  minWidthOfBody?: boolean;
  autoClickFirst?: boolean,
  afterGetData?: (data: StockDetail[], options: TableOptions) => any,
  selectedHighLight?: boolean;
  responsive?: {
    enabled?: boolean,
    rowHeight?: number
  };
  parts?: {
    order?: boolean,
    emptyRow?: boolean,
    header?: boolean,
  }
}

const defaultTableOptions: DynamicTableOptions = {
  subscribe: {
    enabled: false,
    channels: []
  },
  cacheSize: 0.5,
  autoLoadMore: true,
  afterGetData: (data, options) => data,
  minWidthOfBody: true,
  autoClickFirst: true,
  selectedHighLight: true,
  responsive: {
    enabled: true,
    rowHeight: 30
  },
  parts: {
    order: false,
    emptyRow: true,
    header: true
  },
};

const SOCKET_CHANNEL = "dynPanel";

const tableHeaders: QuoteHeaders = {
  headHeader: [
    {
      label: "序号",
      key: "$order",
      class: ["table-cell-order"]
    },
    {
      label: "名称",
      key: "prod_name",
      class: ["table-cell-name"]
    },
    {
      label: "股票代码",
      key: "symbol",
      class: ["table-cell-short"]
    }
  ],
  bodyHeader: [
    {
      label: "现价",
      key: "last_px",
      withUpOrDown: true,
    }, {
      label: "涨跌幅",
      key: "px_change_rate",
      withUpOrDown: true,
      withPercent: true
    }, {
      label: "涨跌",
      key: "px_change",
      withUpOrDown: true,
    }, {
      label: "成交量",
      key: "business_amount",
      withUnit: true,
      transform: scaleToCent
    }, {
      label: "成交额",
      key: "business_balance",
      withUnit: true,
    }, {
      label: "换手率",
      key: "turnover_ratio",
      withPercent: true
    }, {
      label: "量比",
      key: "vol_ratio",
    }, {
      label: "委比",
      key: "entrust_rate",
      withPercent: true
    }, {
      label: "振幅",
      key: "amplitude",
      withPercent: true
    }, {
      label: "内盘",
      key: "business_amount_in",
      withUnit: true,
      transform: scaleToCent
    }, {
      label: "外盘",
      key: "business_amount_out",
      withUnit: true,
      transform: scaleToCent
    }, {
      label: "流通市值",
      key: "circulation_value",
      withUnit: true
    }, {
      label: "市盈率",
      key: "pe_rate",
    }, {
      label: "市净率",
      key: "dyn_pb_rate",
    }
  ]
};


const getEmptyEntry = (addEmptyFlag = true) =>
  [...tableHeaders.bodyHeader, ...tableHeaders.headHeader]
    .reduce((acc, cur) => {
      return {
        ...acc,
        [cur.key]: ""
      };
    }, { $isEmpty: addEmptyFlag });

interface StoreState {
  size: number,
  offset: number,
  ticks: any[],
  ticksToShow?: any[]
  sticky: boolean;
}

const calcNewOffset = (state: StoreState, newDataArray) =>
  Math.floor(state.ticks.length * state.size) / newDataArray.length;

interface LoadMoreDataParams extends TableParams {
  page: number;
  perPage: number;
  updatePage: boolean;
}

@Component({
  // moduleId: module.id,
  selector: 'dynamic-table',
  templateUrl: 'dynamic-table.component.html',
  styleUrls: [
    "dynamic-table.component.less"
  ],
  inputs: [
    "updateEventSource",
    "scrollable",
    "options",
    "headers"
  ],
  outputs: [
    "stockClick",
    "stockDblClick"
  ]
})
export class DynamicTableComponent implements OnInit {
  currentSort;
  currentSortType;
  currentSelected;
  dataUrl: string = "";
  dynamicDataSource: Subject<StoreState> = new Subject();
  emptyRowData = [];
  updateEventSource: Observable<any>; //触发更新数据的事件源；
  fullData = [];
  fullDataLoaded = false;
  headers = tableHeaders;
  // 选中高亮 
  highLightStockSymbol: String;
  quoteData;
  loadMoreDataSub: Subject<LoadMoreDataParams> = new Subject();
  loadingData = false;
  state = {
  };
  httpParams = {
    page: 1,
    perPage: 1,
    sortBy: "",
    sortType: ""
  };
  store; // 变量名起的不好；实际上是用来维护滚动条状态的，与ScrollBar联动仓库；
  subscription: Subscription;
  scrollBarSource = new Subject<any>();
  scrollResetSub = new Subject<any>();
  scrollPos = {
    pos: 0
  };
  socketHandler;
  stockClick = new EventEmitter();
  stockDblClick = new EventEmitter();
  subParams: TableParams;
  tableMinTop;
  tableMinBottom;
  visibleSize;
  _options;
  set options(customOptions) {
    this._options = _.merge({}, defaultTableOptions, customOptions);
    // this._options = Object.assign({}, defaultTableOptions, customOptions);
  }
  get options(): TableOptions {
    return this._options;
  }

  @ViewChild("tableMain", { read: ElementRef })
  tableMain;
  @ViewChild("tableContainer", { read: ElementRef })
  tableContainer: ElementRef;

  constructor(
    private loader: LoadingService,
    private broadcast: BroadcastService,
    private elem: ElementRef,
    private http: Http,
    private socket: SocketService
  ) { }

  ngOnInit() {
    if (!this.options) {
      this.options = {}
    }

    this.socketHandler = data => {
      // // console.debug("Dynamic Table Socket: ", data);
      this.quoteData =
        this.quoteData.map(v =>
          v.symbol && v.symbol in data
            ? { ...v, ...covertCmToHs(data[v.symbol]) }
            : v
        );
    };

    this.socket.on(SOCKET_CHANNEL, this.socketHandler);
    this.subscription = this.loadMoreDataSub
      .debounceTime(100)
      .switchMap(params => {
        let lastPageParams = this.getHttpParams(
          params,
          params.page || this.httpParams.page,
          params.perPage || this.httpParams.perPage,
          this.httpParams.sortBy,
          this.httpParams.sortType
        );
        let nextPageParams = this.getHttpParams(
          params,
          params.page + 1 || this.httpParams.page + 1,
          params.perPage || this.httpParams.perPage,
          this.httpParams.sortBy,
          this.httpParams.sortType
        );
        // // console.debug("Dynamic Table Http Params: ", lastPageParams);
        if (lastPageParams["method"] === "post") {
          return Observable.zip(
            this.http.post(lastPageParams.dataUrl, lastPageParams.body)
              .map(res => res.json())
              .catch(err => Observable.of([])),
            this.http.post(nextPageParams.dataUrl, nextPageParams.body)
              .map(res => res.json())
              .catch(err => Observable.of([])),
            (lastPage, newPage) =>
              [...lastPage, ...newPage]
          )
            .map(data => {
              return {
                ...params,
                data,
              };
            });
        }
        return Observable.zip(
          this.http.get(lastPageParams.dataUrl)
            .map(res => res.json())
            .catch(err => Observable.of([])),
          this.http.get(nextPageParams.dataUrl)
            .map(res => res.json())
            .catch(err => Observable.of([])),
          (lastPage, newPage) =>
            [...lastPage, ...newPage]
        )
          .map(data => {
            return {
              ...params,
              data,
            };
          });
      })
      .subscribe(params => {
        this.loadingData = false;
        if (params.data.length < params.perPage * 2) {
          this.fullDataLoaded = true;
        }
        // console.debug("Dynamic Table: ", params);
        this.patchFullData(params.data, params.page, params.perPage);
        this.updateScrollBar();
        if (params.updatePage) {
          this.httpParams.page = params.page;
        }
      });
  }

  ngAfterViewInit() {
    let updateSource = this.updateEventSource.publishReplay(1);
    updateSource.connect();
    this.subscription.add(
      updateSource.skip(1).subscribe(() => this.scrollResetSub.next({}))
    );
    this.subscription.add(
      updateSource.subscribe((params: TableParams) => {
        let size = this.getPageSize();
        this.currentSelected = undefined;
        this.fullData = new Array(size).fill([]);
        this.fullDataLoaded = false;
        this.updateVisibleData({ pos: 0 });
        this.dataUrl = params.dataUrl;
        this.subParams = params;
        this.httpParams.page = 1;
        this.httpParams.sortType = "1";
        this.httpParams.sortBy = "px_change_rate";
        this.httpParams.perPage = size * 2;
        this.loadMoreData(this.httpParams.page, this.httpParams.perPage);
      })
    );
    // 响应重绘事件
    this.subscription.add(
      this.broadcast.broadcast
        .filter(action => action.type === BROADCAST_TYPE.RENDER_CHAERT)
        .subscribe(action => {
          this.handleScroll(this.scrollPos);
        })
    );
  }

  ngOnDestroy() {
    // 退订Socket
    if (this.quoteData) {
      this.socket.emit("unsub", { channel: SOCKET_CHANNEL, symbols: (<any[]>this.quoteData).map(quote => quote.symbol) });
    }
    if (this.socketHandler) {
      this.socket.off(SOCKET_CHANNEL, this.socketHandler);
    }
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }

  getHttpParams(params: TableParams, page: number, perPage: number, sortBy?: string, sortType?: string) {
    if (params.method && params.method.toLowerCase() === "post") {
      let body = Object.assign({}, params.body, {
        sortBy: this.currentSort,
        sortType: this.currentSortType,
        page,
        perPage
      });
      return {
        ...params,
        body
      };
    }
    let url = appendParams(params.dataUrl, "page", page.toString());
    url = appendParams(url, "perPage", perPage.toString());
    url = appendParams(url, "sortBy", sortBy || "px_change_rate");
    url = appendParams(url, "sortType", sortType || "1");
    return {
      ...params,
      dataUrl: url
    };
  }

  updateScrollBar() {
    this.scrollBarSource.next({
      sticky: false,
      // sticky: state.sticky,
      amountOfTotal: this.fullData.length,
      amountOfVisible: this.visibleSize,
      byContainer: true,
    });
  }

  handleScroll(pos) {
    this.scrollPos = pos;
    this.updateVisibleData(pos);
    if (pos.sticky && !this.fullDataLoaded && !this.loadingData) {
      this.loadingData = true;
      this.appendEmptyData(this.httpParams.perPage);
      this.updateScrollBar();
      this.loadMoreData(this.httpParams.page + 1, this.httpParams.perPage, true);
    } else {
      this.loadMoreData(getPageNumByOffsetRate(this.fullData.length, this.httpParams.perPage, pos.pos), this.httpParams.perPage, true)
    }
  }

  handleResize(e) {
    this.handleScroll(this.scrollPos);
  }

  updateVisibleData(pos) {
    // console.debug("Scroll Pos: ", pos, this.fullData);
    this.getPageSize();
    // 有先前内容，退订，订阅新条目推送
    if (this.quoteData) {
      this.socket.emit("unsub", { channel: SOCKET_CHANNEL, symbols: (<any[]>this.quoteData).map(quote => quote.symbol) });
    }
    setTimeout(() => {
      this.quoteData = [...this.fullData.slice(
        Math.floor(pos.pos * this.fullData.length),
        Math.floor(pos.pos * this.fullData.length + this.visibleSize)
      )];
      try {
        let next = Math.floor(pos.pos * this.fullData.length + this.visibleSize);
        this.emptyRowData = this.fullData.slice(next, next + 1);
      } catch (e) {
        this.emptyRowData = [];
      }
      if (!this.currentSelected && this.quoteData.length) {
        this.currentSelected = this.quoteData[0].symbol;
        this.stockClick.emit(this.quoteData[0]);
      }
      this.socket.emit("sub", { channel: SOCKET_CHANNEL, symbols: (<any[]>this.quoteData).map(quote => quote.symbol) });

    });

  }

  appendEmptyData(size) {
    for (let i = 0; i < size; i++) {
      this.fullData.push(getEmptyEntry());
    }
  }

  // Copy from pagination
  getPageSize() {
    // console.debug("TBALE GET SIZE: ", this.tableContainer, this.tableMain);
    // 待调整
    let tableContainerLook = this.tableContainer.nativeElement.getBoundingClientRect();
    let tableTop = tableContainerLook.top;
    this.tableMinTop = Math.abs(tableContainerLook.top - tableTop);
    this.tableMinBottom = Math.abs(tableContainerLook.bottom);
    let tableBottom = tableContainerLook.bottom;
    let validHeight = Math.abs(tableTop - tableBottom);
    let size = this.visibleSize = Math.floor(validHeight / (this.options.responsive.rowHeight - 0.1));
    return size;
  }

  loadMoreData(page, perPage, updatePage = false) {
    this.loadMoreDataSub.next({
      ...this.subParams,
      page,
      perPage,
      updatePage
    });
  }

  patchFullData(data: any[], page: number, perPage: number) {
    let currentOffset;
    if (this.quoteData && this.quoteData.length) {
      // currentOffset = this.quoteData[0].$order - 1;
      currentOffset = Math.max(0, this.quoteData[0].$order - 1);
    }
    let newFullData = [
      ...this.fullData.slice(0, perPage * (page - 1)),
      ...data,
      ...this.fullData.slice(perPage * (page + 1))
    ].filter(entry => !entry.$isEmpty);

    newFullData.forEach((data, i) => data.$order = i + 1);

    this.fullData = this.options.afterGetData(newFullData, this.options);
    this.updateScrollBar();
    if (currentOffset && currentOffset / newFullData.length <= 1) {
      // // console.debug("Current Offset: ", )
      return this.updateVisibleData({
        // pos: currentOffset / newFullData.length < 0 ? 1 : currentOffset / newFullData.length // todo 判断此处会导致滚动条不会过分加载
        pos: currentOffset / newFullData.length
      });
    }
    if (this.scrollPos) {
      return this.updateVisibleData(this.scrollPos);
    }
    return this.updateVisibleData({ pos: 0 });
  }

  trySortData(header: QuoteHeader) {
    if (!SORT_LIST || SORT_LIST.indexOf(header.key) === -1) return;
    if (this.httpParams.sortBy === header.key) {
      this.httpParams.sortType = this.httpParams.sortType === "0" ? "1" : "0";
    } else {
      this.httpParams.sortBy = this.currentSort = header.key;
      this.httpParams.sortType = "1";
    }
    this.loadMoreData(this.httpParams.page, this.httpParams.perPage);
  }

  handleStockClick(stock) {
    this.currentSelected = stock.stock.symbol;
    this.stockClick.emit(stock.stock);
  }

  handleStockDblClick(stock) {
    console.error("DynamicTable: ", stock);
    this.stockDblClick.emit(stock.stock);
  }

  onClick(quote) {

  }

  onDblClick(quote) {

  }
}